﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VisionCloud.Core;
using VisionCloud.Core.Model;
using VisionCloud.Domain.Common;
using VisionCloud.Domain.Extension;

namespace VisionCloud.Domain.Repository
{
    public class BaseRepository<T> : IDisposable, IBaseRepository<T> where T : class, new()
    {
        public BlueDbContext Db { get; set; }

        public IQueryable<T> GetAll() => Db.Set<T>();

        public bool Any(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ? Db.Set<T>().Any() : Db.Set<T>().Any(expression);
        }

        public int Count(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ? Db.Set<T>().Count() : Db.Set<T>().Count(expression);
        }

        public T FirstOrDefault(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ? Db.Set<T>().FirstOrDefault() : Db.Set<T>().FirstOrDefault(expression);
        }

        public Task<T> LastAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().LastAsync() : Db.Set<T>().LastAsync(expression);
        }

        public List<T> Where(Expression<Func<T, bool>> expression)
        {
            return Db.Set<T>().Where(expression).AsNoTracking().ToList();
        }

        public async Task<List<T>> WhereAsync(Expression<Func<T, bool>> expression)
        {
            return expression == null ?
                await Db.Set<T>().ToListAsync() : await Db.Set<T>().Where(expression).ToListAsync();
        }

        public List<T> WhereAsNoTracking(Expression<Func<T, bool>> expression)
        {
            return Db.Set<T>().Where(expression).AsNoTracking().ToList();
        }

        public async Task<List<T>> WhereAsNoTrackingAsync(Expression<Func<T, bool>> expression)
        {
            return expression == null ?
                await Db.Set<T>().AsNoTracking().ToListAsync() : await Db.Set<T>().Where(expression).AsNoTracking().ToListAsync();
        }

        public T GetById(object id)
        {
            return Db.Find<T>(id);
        }

        public async Task<T> GetByIdAsync(object id)
        {
            return await Db.FindAsync<T>(id);
            //  return Db.FindAsync<T>(id);
        }

        public Task<bool> AnyAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().AnyAsync() : Db.Set<T>().AnyAsync(expression);
        }

        public Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().FirstOrDefaultAsync() : Db.Set<T>().FirstOrDefaultAsync(expression);
        }

        public T LastOrDefault(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().LastOrDefault() : Db.Set<T>().LastOrDefault(expression);
        }

        public Task<T> LastOrDefaultAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().LastOrDefaultAsync() : Db.Set<T>().LastOrDefaultAsync(expression);
        }

        public T SingleOrDefault(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().SingleOrDefault() : Db.Set<T>().SingleOrDefault(expression);
        }

        public Task<T> SingleOrDefaultAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().SingleOrDefaultAsync() : Db.Set<T>().SingleOrDefaultAsync(expression);
        }

        public T Single(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().Single() : Db.Set<T>().Single(expression);
        }

        public Task<T> SingleAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().SingleAsync() : Db.Set<T>().SingleAsync(expression);
        }

        public T First(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().First() : Db.Set<T>().First(expression);
        }

        public Task<T> FirstAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().FirstAsync() : Db.Set<T>().FirstAsync(expression);
        }

        public T Last(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
                Db.Set<T>().Last() : Db.Set<T>().Last(expression);
        }

        public async Task<int> CountAsync(Expression<Func<T, bool>> expression = null)
        {
            return expression == null ?
               await Db.Set<T>().CountAsync() : await Db.Set<T>().CountAsync(expression);
        }

        public int ExecuteSqlCommand(string sql, params object[] parameters)
        {
            return Db.Database.ExecuteSqlCommand(sql, parameters);
        }

        public IQueryable<T> Query()
        {
            return Db.Set<T>().Where(w => true);
        }

        public void Add(T model, bool autoSetOperatorInfo = true)
        {
            if (autoSetOperatorInfo && typeof(T).IsSubclassOf(typeof(CreateEntity)))
            {
                ((CreateEntity)Convert.ChangeType(model, typeof(T))).SetCreateOperation();
            }

            if (autoSetOperatorInfo && typeof(T).IsSubclassOf(typeof(BaseEntity)))
            {
                ((BaseEntity)Convert.ChangeType(model, typeof(T))).SetOperatorInfoWhenAdd();
            }

            Db.Set<T>().Add(model);
        }

        public async Task Add(AnyValidModel<T> validModel, T model, bool autoSetOperatorInfo = true)
        {
            if (await AnyAsync(validModel.Express) && validModel.ThrowException)
            {
                throw new OperationException(validModel.ErrorMessage);
            }
            else
            {
                Add(model, autoSetOperatorInfo);
            }
        }

        public void AddRange(IEnumerable<T> models, bool autoSetOperatorInfo = true)
        {
            if (autoSetOperatorInfo)
            {
                foreach (var model in models)
                {
                    if (typeof(T).IsSubclassOf(typeof(BaseEntity)))
                    {
                        ((BaseEntity)Convert.ChangeType(model, typeof(T))).SetOperatorInfoWhenAdd();
                    }
                }
            }
            Db.Set<T>().AddRange(models);
        }

        public async Task<T> GetOrAdd(Expression<Func<T, bool>> expression, T model, bool autoSetOperatorInfo = true)
        {
            var entity = await SingleOrDefaultAsync(expression);
            if (entity != null)
                return entity;

            Add(model, autoSetOperatorInfo);
            return model;
        }

        public void Update(T entity, bool autoSetOperatorInfo = true)
        {
            Db.Set<T>().Attach(entity);
            Db.Entry(entity).State = EntityState.Modified;

            if (autoSetOperatorInfo && typeof(T).IsSubclassOf(typeof(BaseEntity)))
            {
                ((BaseEntity)Convert.ChangeType(entity, typeof(T))).SetOperatorInfoWhenUpdate();
                Db.Entry(entity).Property("CreatedBy").IsModified = false;
                Db.Entry(entity).Property("CreatedDate").IsModified = false;
            }
        }

        public async Task Update(AnyValidModel<T> validModel, T model, bool autoSetOperatorInfo = true)
        {
            if (await AnyAsync(validModel.Express) && validModel.ThrowException)
            {
                throw new OperationException(validModel.ErrorMessage);
            }
            else
            {
                Update(model, autoSetOperatorInfo);
            }
        }

        public void Update(T model, Expression<Func<T, object>> handleField, bool inUse = true)
        {
            Db.Set<T>().Attach(model);
            var entry = Db.Entry(model);
            var fields = GetMemberAccessArray(handleField);
            if (inUse)
            {
                fields.ForEach(p => entry.Property(p).IsModified = true);
            }
            else
            {
                entry.State = EntityState.Modified;
                fields.ForEach(p => entry.Property(p).IsModified = false);
            }
        }

        public void Remove(T entity, bool realDelete = false)
        {
            Db.Set<T>().Attach(entity);
            if (!realDelete && typeof(T).IsSubclassOf(typeof(BaseEntity)))
            {
                ((BaseEntity)Convert.ChangeType(entity, typeof(T))).SetOperatorInfoWhenRemove();
                var entry = Db.Entry(entity);
                entry.Property("IsDeleted").IsModified = true;
                entry.Property("LastUpdatedBy").IsModified = true;
                entry.Property("LastUpdatedDate").IsModified = true;
            }
            else
            {
                Db.Set<T>().Remove(entity);
            }
        }

        public async Task Remove(AnyValidModel<T> validModel, T model, bool realDelete = false)
        {
            if (await AnyAsync(validModel.Express) && validModel.ThrowException)
            {
                throw new OperationException(validModel.ErrorMessage);
            }
            else
            {
                Remove(model, realDelete);
            }
        }

        public void RemoveRange(List<T> list, bool realDelete = false)
        {
            Db.Set<T>().AttachRange(list);
            if (!realDelete && typeof(T).IsSubclassOf(typeof(BaseEntity)))
            {
                foreach (var entity in list)
                {
                    ((BaseEntity)Convert.ChangeType(entity, typeof(T))).SetOperatorInfoWhenRemove();
                    var entry = Db.Entry(entity);
                    entry.Property("IsDeleted").IsModified = true;
                    entry.Property("LastUpdatedBy").IsModified = true;
                    entry.Property("LastUpdatedDate").IsModified = true;
                }
            }
            else
            {
                foreach (var entity in list)
                {
                    Remove(entity, true);
                }
            }
        }

        public void RemoveById(object id, bool realDelete = false)
        {
            var entity = GetById(id);
            if (!realDelete && typeof(T).IsSubclassOf(typeof(BaseEntity)))
            {
                ((BaseEntity)Convert.ChangeType(entity, typeof(T))).SetOperatorInfoWhenRemove();
                var entry = Db.Entry(entity);
                entry.Property("IsDeleted").IsModified = true;
                entry.Property("LastUpdatedBy").IsModified = true;
                entry.Property("LastUpdatedDate").IsModified = true;
            }
            else
            {
                Db.Set<T>().Remove(entity);
            }
        }

        public void UpdateToDeleteRange(IEnumerable<T> models)
        {
            foreach (var entity in models)
            {
                var entry = Db.Entry(entity);
                entry.State = EntityState.Modified;
                if (typeof(T).IsSubclassOf(typeof(BaseEntity)))
                {
                    ((BaseEntity)Convert.ChangeType(entity, typeof(T))).SetOperatorInfoWhenRemove();
                    entry.Property("IsDeleted").IsModified = true;
                    entry.Property("LastUpdatedBy").IsModified = true;
                    entry.Property("LastUpdatedDate").IsModified = true;
                }
                else
                {
                    Db.Set<T>().Remove(entity);
                }
            }
        }

        #region ~辅助方法

        /// <summary>
        /// 获取属性
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="express"></param>
        /// <returns></returns>
        private static List<string> GetMemberAccessArray<TEntity>(Expression<Func<TEntity, object>> express)
        {
            switch (express.Body.NodeType)
            {
                case ExpressionType.New:
                    return
                        ((NewExpression)express.Body).Members.Select(w => w.Name).ToList();
                case ExpressionType.MemberAccess:
                    return new List<string>() { ((MemberExpression)express.Body).Member.Name };

                case ExpressionType.Convert:
                    return new List<string>() { ((MemberExpression)((UnaryExpression)express.Body).Operand).Member.Name };

                default:
                    throw new Exception("暂不支持其他形式");
            }
        }

        #endregion ~辅助方法

        public void UpdateToDeleted(T model)
        {
            Db.Set<T>().Attach(model);
            var entry = Db.Entry(model);
            //Db.Entry(model).State = EntityState.Modified;
            Db.Entry(model).Property("IsDeleted").IsModified = true;
            Db.Entry(model).Property("LastUpdatedBy").IsModified = true;
            Db.Entry(model).Property("LastUpdatedDate").IsModified = true;
        }

        /// <summary>
        /// 分页查询异步
        /// </summary>
        /// <param name="whereLambda">查询添加（可有，可无）</param>
        /// <param name="orderBy">排序条件（一定要有）</param>
        /// <param name="pageIndex">当前页码</param>
        /// <param name="pageSize">每页大小</param>
        /// <param name="isOrder">排序正反</param>
        /// <returns></returns>
        public async Task<ApiPageResult<T>> GetPageAsync<TKey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, TKey>> orderBy, int pageIndex, int pageSize, bool isOrder = true, bool isNoTracking = true)
        {
            IQueryable<T> data = isOrder ? Db.Set<T>().OrderBy(orderBy) : Db.Set<T>().OrderByDescending(orderBy);

            if (whereLambda != null)
            {
                data = isNoTracking ? data.Where(whereLambda).AsNoTracking() : data.Where(whereLambda);
            }
            ApiPageResult<T> pageData = new ApiPageResult<T>
            {
                Total = await data.CountAsync(),
                PageData = await data.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync()
            };
            pageData.TotalPage = (int)pageData.Total / pageSize + (pageData.Total % pageSize > 0 ? 1 : 0);
            pageData.CurrentPage = pageIndex;
            return pageData;
        }

        public T Get<TPrimaryKey>(TPrimaryKey key, string keyColumnName)
        {
            var entity = FirstOrDefault(key, keyColumnName);

            return entity;
        }

        public T FirstOrDefault<TPrimaryKey>(TPrimaryKey key, string keyColumnName)
            => Db.Set<T>().FirstOrDefault(CreateEqualityExpressionForId<TPrimaryKey>(key, keyColumnName));

        public T Update(Expression<Func<T, bool>> equalityExprssionForId, Action<T> updateAction)
        {
            var entity = Db.Set<T>().FirstOrDefault(equalityExprssionForId);
            updateAction(entity);
            return entity;
        }

        public T Update<TPrimaryKey>(TPrimaryKey key, string keyColumnName, Action<T> updateAction)
        {
            var entity = Get(key, keyColumnName);
            updateAction(entity);
            return entity;
        }

        public T Update(Object id, Action<T> updateAction)
        {
            var entity = GetById(id);
            if (entity == null)
            {
                return null;
            }
            updateAction(entity);
            Update(entity, true);
            return entity;
        }

        public Expression<Func<T, bool>> CreateEqualityExpressionForId<TPrimaryKey>(TPrimaryKey key, string keyColumnName)
        {
            var lambdaParam = Expression.Parameter(typeof(T));

            var leftExpression = Expression.PropertyOrField(lambdaParam, keyColumnName);

            Expression<Func<object>> closure = () => key;
            var rightExpression = Expression.Convert(closure.Body, leftExpression.Type);

            var lambdaBody = Expression.Equal(leftExpression, rightExpression);

            return Expression.Lambda<Func<T, bool>>(lambdaBody, lambdaParam);
        }

        public void Update(T entity, List<string> updatePropertyList = null, bool modified = true)
        {
            Db.Set<T>().Attach(entity);
            var entry = Db.Entry(entity);
            if (updatePropertyList == null)
            {
                entry.State = EntityState.Modified;//全字段更新
            }
            else
            {
                if (modified)
                {
                    updatePropertyList.ForEach(c =>
                    {
                        entry.Property(c).IsModified = true; //部分字段更新的写法
                    });
                }
                else
                {
                    entry.State = EntityState.Modified;//全字段更新
                    updatePropertyList.ForEach(c =>
                    {
                        entry.Property(c).IsModified = false; //部分字段不更新的写法,没测试可能有BUG
                    });
                }
            }
        }

        public virtual async Task DeleteAsync(Expression<Func<T, bool>> predicate)
        {
            var entities = await GetAll().Where(predicate).ToListAsync();
            if (entities != null && entities.Count > 0)
            {
                Db.Set<T>().RemoveRange(entities);
            }
        }

        public virtual Task DeleteAsync(T entity)
        {
            Db.Set<T>().Remove(entity);
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            //Db?.Dispose();
            //GC.SuppressFinalize(this);
        }
    }
}